﻿#ifndef VIDEOPLAYER_H
#define VIDEOPLAYER_H

#include <QDebug>
#include <QThread>
#include "videoConfig.h"
#include "audioDecode.h"
#include "videoDecode.h"


//ffmpeg回调函数, 内部处理失败会调用
//FFMPEG回调函数，返回1表示超时  0表示正常
static int interrupt_cb(void *ctx)
{
    Q_UNUSED(ctx);
    printf("interrupt_cb: \n\n\n");
    return 0;
}

class videoPlayer : public QThread
{
    Q_OBJECT
public:
    explicit videoPlayer(QObject* parent);
    ~videoPlayer();
    ///////////////////////////////// 信号槽的连接 //////////////////////////////
    /**
     * @brief           与parent之间的信号槽连接
     * @author          wq
     * @time            2020-12-11
     * @param
     * @return
     * @remarks
     */
    void connectSignalAndSlotToParent();
    void disconnectSignalAndSlotToParent();

    /**
     * @brief           与视频音频解码线程之间的信号槽连接
     * @author          wq
     * @time            2020-12-11
     * @param
     * @return
     * @remarks
     */
    void connectSignalAndSlotToAudioVideoThread();
    void disconnectSignalAndSlotToAudioVideoThread();


    /////////////////////////////////// 线程控制 //////////////////////////////
    int threadStartUp();
    /**
    * @param           bool pause: true改变状态为PLAY_PAUSE false改变执行状态为PLAY_RUNNING
    */
    int threadPause(bool pause);
    int threadQuit();
    int threadSeek();

    ////////////////////////////////// 上层操作函数 /////////////////////////////

    // start之前不需要调用stop函数，在start里面会自动stop
    void playStart(QString videoName);
    void playStop();
    void playPause();
    void playContinue();
    void playSeek(int value);
    void playChangeShowQImageSize(QSize size);

    /////////////////////////////// 往上层传递一些消息 //////////////////////////
    void sendThreadStartSuccess()
    {
        emit threadStartSuccessSignal(this->getVideoAllTime());//发送的是视频总时间
    }
    void sendFinishedVideoPlay()//发送播放完成信号
    {
        emit finishedVideoPlaySignal();
    }
    void sendFinishedVideoPlayThread()//播放线程结束信号
    {
        emit finishedVideoPlayThreadSignal();
    }

    void sendPlayTime()
    {
        emit updatePlayTimeSignal(this->UIPlay.videoPlayTime, this->UIPlay.videoAllTime);
    }
    void sendPlayImg(QImage img)
    {
        emit this->updatePlayImgSignal(img);
    }
    ///////////////////////////////// 信号槽的定义 //////////////////////////////
public slots:
    void startVideoPlaySlot(QString videoName);//开始播放
    /**
     * @brief           关闭当前视频文件,清除当前解码器和视频数据
     * @author          wq
     * @time            2020-12-11
     * @param
     * @return
     * @remarks
     */
    void threadStartSuccessSlot();
    void finishedVideoPlaySlot();
    void stopVideoPlaySlot();
    void pauseVideoPlaySlot(bool pause);
    void seekVideoPlaySlot(int value);
    void updatePlayTimeFromVideoThreadSlot(quint64 playTime);//更新播放时间, 以视频时间更新
    void updatePlayImgFromVideoThreadSlot(QImage img);//更新播放图像

signals:
    void startVideoPlaySignal(QString videoName);//启动播放 信号
    void threadStartSuccessSignal(quint64 videoAllTime);//线程启动成功信号,传递的是视频总时间
    void finishedVideoPlaySignal();//播放完成信号
    void finishedVideoPlayThreadSignal(); //播放线程结束信号
    void stopVideoPlaySignal();//关闭当前播放的视频 信号
    void pauseVideoPlaySignal(bool pause);//暂停播放播放 信号
    void seekVideoPlaySignal(int value); // 进行跳转 value 是 进度条的示数, 取值在 0~100 之间
    void updatePlayTimeSignal(int playTime, quint64 videoAllTime);//更新播放时间
    void updatePlayImgSignal(QImage img);//更新播放图像


public:
    ///////////////////////////////// 视频解码相关 //////////////////////////////
    /**
    * @brief           获取设置视频文件名
    * @author          wq
    * @time            2020-12-11
    */
    inline void setVideoName(QString &fileName)
    {
        this->videoName = fileName;
        qWarning()<< "setVideoName(QString &fileName):  videoName: "<< this->videoName;
    }
    inline QString getVideoName()
    {
        return this->videoName;
    }
    inline quint64 getVideoAllTime()
    {
        return this->UIPlay.videoAllTime;
    }
    inline DecodingRelated* getDecodingRelated()
    {
        return &this->Decoding;
    }
    inline PlayRelated* getPlayRelated()
    {
        return &this->UIPlay;
    }
    inline int AVFormatContextInit()
    {
        /*
         * 2. 分配解码上下文
        这个结构体描述了一个媒体文件或媒体流的构成和基本信息。
        这是FFMpeg中最为基本的一个结构，是其他所有结构的根，是一个多媒体文件或流的根本抽象。
        */
        this->Decoding.format_ctx = avformat_alloc_context();
        if(this->Decoding.format_ctx == Q_NULLPTR)
        {
            qDebug()<< tr("AVFormatContextInit():  avformat_alloc_context() 分配 AVFormatContext 空间失败!");
            return -1;
        }
        //注册回调函数
        this->Decoding.format_ctx->interrupt_callback.callback = interrupt_cb;
        return 0;
    }

    inline int openVideoFile()
    {
        qDebug()<< tr("openVideoFile(): 打开视频文件!");
        const char* url =  this->videoName.toLocal8Bit().data();
        //const char* url = this->videoName.toUtf8().data();
        qDebug()<< tr("openVideoFile(): videoName-  %1").arg(this->videoName);
        qDebug()<< tr("openVideoFile(): url-  %1").arg(url);
        int ret = -1;
        ret = avformat_open_input(&(this->Decoding.format_ctx), url, nullptr, nullptr);
        if(ret != 0)
        {
            qDebug() << tr("openVideoFile():  打开视频文件出错, 错误码: %1").arg(ret);
            return ret;
        }
        qDebug()<< tr("openVideoFile():  打开视频文件 %1 成功！").arg(this->videoName);
        this->UIPlay.threadControl.videoFileEnd = false;
        return ret;
    }

     /**
     * @brief           获取流信息
     * @author          wq
     * @time            2020-12-11
     * @param
     * @return          正常返回0
     * @remarks
     */
    inline int getVideoAudioStreamInfo()
    {
        int ret;
        qDebug()<< tr("videoPlayer::getVideoAudioStreamInfo():  获取流信息！");
        ret = avformat_find_stream_info(this->Decoding.format_ctx, nullptr);
        if(ret < 0)
        {
            qDebug()<<QString("videoPlayer::getVideoAudioStreamInfo():  无法获取流信息: 错误码 %1\n").arg(ret);

        }
        else
        {
            qDebug()<<QString("videoPlayer::getVideoAudioStreamInfo():  流的数量: %1")  \
                                            .arg(this->Decoding.format_ctx->nb_streams);
        }
        return ret;
    }

     /**
     * @brief           根据获取的流寻找解码器并打开解码器
     * @author          wq
     * @time            2020-12-11
     * @param
     * @return          正常返回 0
     * @remarks
     */
    int setAudioCodecVideoCodec();

     /**
     * @brief           解码初始化
     * @author          wq
     * @time            2020-12-11
     * @param
     * @return          正常返回 0
     * @remarks
     */
     int decodingInit();

     /**
     * @brief           将结束播放则将解码相关资源全部释放
     * @author          wq
     * @time            2020-12-11
     * @param
     * @return          正常返回0
     * @remarks
     */
     void decodingDelete();

     /**
     * @brief           根据时间戳找到指定帧
     * @author          wq
     * @time            2020-12-11
     * @param
     * @return          正常返回0, 否则返回错误码
     * @remarks
     */
    int seekToDesignatedFrame(AVFormatContext* format_ctx, int videoStreamIndex, quint64 SeekTime, AVPacket* keyframe_pkt);

protected:
    void run();

private:

    /**
    * brief:           解码相关的数据结构
    * author:          wq
    * time:            2020-12-11
    */

    DecodingRelated Decoding;

    // 运行相关数据和状态,与上层对接
    struct PlayRelated UIPlay;

    QString videoName; //视频文件名

    //音频视频解码线程
    audioDecode audioDecodeThread;
    videoDecode videoDecodeThread;
};

#endif // VIDEOPLAYER_H
